// oggconv.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "platform.h"
#include "audio.h"
#include "utf8.h"
#include "madlldlib.h"
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#include "oggconv.h"

static char* g_arrMP3Genre[] = {
	"Blues","Classic Rock","Country","Dance","Disco","Funk","Grunge","Hip-Hop",
	"Jazz","Metal","New Age","Oldies","Other","Pop","R&B","Rap","Reggae","Rock",
	"Techno","Industrial","Alternative","Ska","Death Metal","Pranks","Soundtrack",
	"Euro-Techno","Ambient","Trip Hop","Vocal","Jazz Funk","Fusion","Trance",
	"Classical","Instrumental","Acid","House","Game","Sound Clip","Gospel","Noise",
	"Alternative Rock","Bass","Soul","Punk","Space","Meditative","Instrumental Pop",
	"Instrumental Rock","Ethnic","Gothic","Darkwave","Techno-Industrial","Electronic",
	"Pop Folk","Eurodance","Dream","Southern Rock","Comedy","Cult","Gangsta","Top 40",
	"Christian Rap","Pop Funk","Jungle","Native American","Cabaret","New Wave",
	"Psychadelic","Rave","ShowTunes","Trailer","Lo-Fi","Tribal","Acid Punk","Acid Jazz",
	"Polka","Retro","Musical","Rock & Roll","Hard Rock","Folk","Folk Rock",
	"National Folk","Swing","Fast Fusion","Bebob","Latin","Revival","Celtic",
	"Bluegrass","Avantgarde","Gothic Rock","Progressive Rock","Psychedelic Rock",
	"Symphonic Rock","Slow Rock","Big Band","Chorus","Easy Listening","Acoustic",
	"Humour","Speech","Chanson","Opera","Chamber Music","Sonata","Symphony","Booty Bass",
	"Primus","Porn Groove","Satire","Slow Jam","Club","Tango","Samba","Folklore","Ballad",
	"Power Ballad","Rhytmic Soul","Freestyle","Duet","Punk Rock","Drum Solo","A Capella",
	"Euro House","Dance Hall","Goa","Drum & Bass","Club House","Hardcore","Terror",
	"Indie","BritPop","Negerpunk","Polsk Punk","Beat","Christian Gangsta Rap",
	"Heavy Metal","Black Metal","Crossover","Contemporary Christian","Christian Rock",
	"Merengue","Salsa","Trash Metal","Anime","JPop","SynthPop"
};

static int g_nMP3GenreCount = 148;

static ConvCallback g_ConvCallback = NULL;

static ConvInfo g_ConvInfo;

typedef struct _MP3INFO
{
	char Identify[3]; 
	char Title[31];   
	char Artist[31];  
	char Album[31];   
	char Year[5];	  
	char Comment[29]; 
	char Genre[31];
	char Track[4];
	unsigned int Size;
} MP3INFO;

static MP3INFO MP3Info;

static void __stdcall statistics_func_dec(unsigned long FrameCnt, unsigned long InByteTot, struct mad_header *Header) 
{
	static char *spinner="|/-\\";
	static int spinpoint = 0;
	if (FrameCnt == 1) 
	{
		fprintf(stderr, "frame:%d, tot. bytes:%d, layer:%d, mode:%d\n",
			FrameCnt,
			InByteTot,
			Header->layer, 
			Header->mode);
	}
	else 
	{
		//fprintf(stderr, "frame:%d, tot. bytes:%d\n", FrameCnt, InByteTot);
		fprintf(stderr, "\r");
		fprintf(stderr, "\t[%5.1f%%] %c", 
			InByteTot * 100.0 / MP3Info.Size, spinner[spinpoint++%4]);
		g_ConvInfo.percent = InByteTot * 100.0 / MP3Info.Size;
		if(g_ConvCallback)
			g_ConvCallback(&g_ConvInfo);
	}
}

static void statistics_func_enc(char *fn, ogg_int64_t total, ogg_int64_t done, double time)
{
	static char *spinner="|/-\\";
	static int spinpoint =0;

	fprintf(stderr, "\r");
	fprintf(stderr, "\tEncoding [%2dm%.2ds so far] %c", 
		((int)time)/60, (int)(time - (double)((int)time/60)*60),
		spinner[spinpoint++%4]);
	g_ConvInfo.percent = done*100.0/total;
	if(g_ConvCallback)
		g_ConvCallback(&g_ConvInfo);
}

static void add_tag(vorbis_comment *vc, oe_options *opt, char *name, char *value)
{
	char *utf8;
	/* if(utf8_encode(value, &utf8) >= 0) */
	if (opt->isutf8) {
		/* TODO: Add checking here to verify that 'value' really is
		a valid utf8 string */
		if(name == NULL)
			vorbis_comment_add(vc, value);
		else
			vorbis_comment_add_tag(vc, name, value);
	}
	else if(utf8_encode(value, &utf8) >= 0)
	{
		if(name == NULL)
			vorbis_comment_add(vc, utf8);
		else
			vorbis_comment_add_tag(vc, name, utf8);
		free(utf8);
	}
	else
		fprintf(stderr, ("Couldn't convert comment to UTF-8, cannot add\n"));
}

static void build_comments(vorbis_comment *vc, oe_options *opt)
{
	vorbis_comment_init(vc);

	add_tag(vc, opt, NULL, opt->comments[0]);

	if(opt->title_count)
	{
		add_tag(vc, opt, "title", opt->title[0]);
	}

	if(opt->artist_count)
	{
		add_tag(vc, opt, "artist", opt->artist[0]);
	}

	if(opt->genre_count)
	{
		add_tag(vc, opt, "genre", opt->genre[0]);
	}

	if(opt->date_count)
	{
		add_tag(vc, opt, "date", opt->dates[0]);
	}

	if(opt->album_count)
	{
		add_tag(vc, opt, "album", opt->album[0]);
	}

	add_tag(vc, opt, "tracknumber", opt->tracknum[0]);

}

static int oggenc(char* wavFile, char* oggFile, MP3INFO* info)
{
	oe_options opt = {NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 
		0, NULL, 0, NULL, 0, NULL, 0, 1, 0, 0, 0, 1, 16, 44100, 2, 0, NULL,
		DEFAULT_NAMEFMT_REMOVE, DEFAULT_NAMEFMT_REPLACE, 
		NULL, 0, -1, -1, -1, 0, .3f, -1, 0, 1, 0, 0.0, 0, 0, 0, 0, 0, 0, 0};

	opt.outfile = oggFile;

	char* tt = info->Title;	
	opt.title = &tt;
	opt.title_count = 1;
	char* at = info->Artist;
	opt.artist = &at;
	opt.artist_count = 1;
	char* ab = info->Album;
	opt.album = &ab;
	opt.album_count = 1;
	char* cm = info->Comment;
	opt.comments = &cm;
	opt.comment_count = 1;
	opt.copy_comments = 1;
	char* tn = info->Track;
	opt.tracknum = &tn;
	opt.track_count = 1;
	char* dt = info->Year;
	opt.dates = &dt;
	opt.date_count = 1;
	char* gr = info->Genre;
	opt.genre = &gr;
	opt.genre_count = 1;

	int errors = 0; 

	/* Now, do some checking for illegal argument combinations */

	if(!opt.fixedserial)
	{
		/* We randomly pick a serial number. This is then incremented for each file */
		srand((unsigned int)time(NULL));
		opt.serial = rand();
		opt.skeleton_serial = opt.serial;
	}

	/* Once through the loop for each file */

	oe_enc_opt      enc_opts;
	vorbis_comment  vc;
	FILE			*in = NULL, *out = NULL;
	int             foundformat = 0;
	char			*artist=NULL, *album=NULL, *title=NULL, *track=NULL;
	char			*date=NULL, *genre=NULL;
	input_format	*format;
	unsigned char   piped_input = 0;
	double          gain_R = 0.0, gain_A = 0.0;
	int             resample_used = 0;

	/* Set various encoding defaults */

	enc_opts.serialno = opt.serial++;
	enc_opts.skeleton_serialno = opt.skeleton_serial++; 
	enc_opts.progress_update = statistics_func_enc;
	enc_opts.start_encode = start_encode_full;
	enc_opts.end_encode = final_statistics;
	enc_opts.error = encode_error;
	enc_opts.comments = &vc;
	enc_opts.copy_comments = opt.copy_comments;
	enc_opts.with_skeleton = opt.with_skeleton;
	enc_opts.FLAC_scale = opt.FLAC_scale;

	/* OK, let's build the vorbis_comments structure */
	build_comments(&vc, &opt);
	in = oggenc_fopen(wavFile, "rb", opt.isutf8);

	if(in == NULL)
	{
		fprintf(stderr, ("ERROR: Cannot open input file \"%s\": %s\n"), wavFile, strerror(errno));
		errors++;
		goto clear_all;
	}

	format = open_audio_file(in, &enc_opts);
	if(format)
	{
		if(!opt.quiet)
			fprintf(stderr, ("Opening with %s module: %s\n"), 
			format->format, format->description);
		foundformat=1;
	}


	if(!foundformat)
	{
		fprintf(stderr, ("ERROR: Input file \"%s\" is not a supported format\n"), wavFile?wavFile:"(stdin)");
		errors++;
		goto clear_all;
	}

	if(out == NULL)
	{
		/* Create any missing subdirectories, if possible */
		if(create_directories(oggFile, opt.isutf8)) 
		{
			fprintf(stderr, ("ERROR: Could not create required subdirectories for output filename \"%s\"\n"), oggFile);
			errors++;
			goto clear_all;
		}

		if(wavFile && !strcmp(wavFile, oggFile)) 
		{
			fprintf(stderr, ("ERROR: Input filename is the same as output filename \"%s\"\n"), oggFile);
			errors++;
			goto clear_all;
		}

		/* out = fopen(out_fn, "wb"); */
		out = oggenc_fopen(oggFile, "wb", opt.isutf8);
		if(out == NULL)
		{
			fprintf(stderr, ("ERROR: Cannot open output file \"%s\": %s\n"), oggFile, strerror(errno));
			errors++;
			goto clear_all;
		}	
	}

	/* Now, set the rest of the options */
	enc_opts.out = out;
	enc_opts.comments = &vc;

	if (opt.isutf8) {
		utf8_decode(oggFile, &enc_opts.filename);
		utf8_decode(wavFile, &enc_opts.infilename);

	} else {
		enc_opts.filename = oggFile;
		enc_opts.infilename = wavFile;
	}
	enc_opts.managed = opt.managed;
	enc_opts.bitrate = opt.nominal_bitrate; 
	enc_opts.min_bitrate = opt.min_bitrate;
	enc_opts.max_bitrate = opt.max_bitrate;
	enc_opts.quality = opt.quality;
	enc_opts.quality_set = opt.quality_set;
	enc_opts.padding = opt.padding*1024;
	enc_opts.advopt = opt.advopt;
	enc_opts.advopt_count = opt.advopt_count;

	if(opt.resamplefreq && opt.resamplefreq != enc_opts.rate) 
	{
		int fromrate = enc_opts.rate;
		resample_used = 1;
		enc_opts.resamplefreq = opt.resamplefreq;
		enc_opts.converter = opt.converter;
		enc_opts.ratio = (1.0 * opt.resamplefreq) / enc_opts.rate ;
		if(setup_resample(&enc_opts)) {
			errors++;
			goto clear_all;
		}
		else if(!opt.quiet)
			fprintf(stderr, ("Resampling input from %d Hz to %d Hz\n"), fromrate, opt.resamplefreq);
	}

	if(opt.downmix) {
		if(enc_opts.channels == 2) {
			setup_downmix(&enc_opts);
			if(!opt.quiet)
				fprintf(stderr, ("Downmixing stereo to mono\n"));
		}
		else {
			fprintf(stderr, ("ERROR: Can't downmix except from stereo to mono\n"));
			errors++;
			if(opt.resamplefreq)
				clear_resample(&enc_opts);
			goto clear_all;
		}
	}


	if(opt.scale > 0.f) 
	{
		setup_scaler(&enc_opts, opt.scale);
		if(!opt.quiet)
			fprintf(stderr, ("Scaling input to %f\n"), opt.scale);
	}

	if(!enc_opts.total_samples_per_channel)
		enc_opts.progress_update = statistics_func_enc;

	if(opt.quiet)
	{
		enc_opts.start_encode = start_encode_null;
		enc_opts.progress_update = update_statistics_null;
		enc_opts.end_encode = final_statistics_null;
	}

	if(oe_encode(&enc_opts))
		errors++;

	if(opt.scale > 0)
		clear_scaler(&enc_opts);
	if(opt.downmix)
		clear_downmix(&enc_opts);
	if(resample_used == 1)
		clear_resample(&enc_opts);

clear_all:

	if (opt.isutf8) {
		free(enc_opts.filename);
		free(enc_opts.infilename);
	}
	vorbis_comment_clear(&vc);

	format->close_func(enc_opts.readdata);

	if(in)
		fclose(in);

	if(out)
		fclose(out);

	return errors;
}

static int mp3dec(const char* mp3File, const char* wavFile)
{
	int error = 0;
	char statmsg[256];
	error = CbMpegAudioDecoder(mp3File, wavFile, 1, statmsg, statistics_func_dec);

	if (statmsg)
		fprintf(stderr, "%s", statmsg);		

	return error;
}

static int getmp3info(const char* mp3File, MP3INFO& info)
{
	int error = 0;
	memset(&info, 0, sizeof(info));
	FILE* f = NULL;
	f = fopen(mp3File, "rb");
	if(!f)
	{
		error ++;
		goto quit;
	}

	fseek(f, -128L, SEEK_END);
	char buf[128];
	memset(buf, 0, sizeof(buf));
	fread(buf, 1, 128, f);

	if(!((buf[0] == 'T'|| buf[0] == 't')
		&&(buf[1] == 'A'|| buf[1] == 'a')
		&&(buf[2] == 'G'|| buf[0] == 'g')))
	{
		fseek(f, 0, SEEK_END);
		info.Size = ftell(f);
		error ++;
		goto quit;
	}
	try
	{
		memcpy(info.Identify, buf, 3); 
		memcpy(info.Title, buf + 3, 30); 
		memcpy(info.Artist, buf + 33, 30); 
		memcpy(info.Album, buf + 63, 30); 
		memcpy(info.Year, buf + 93, 4); 
		memcpy(info.Comment, buf + 97, 28); 
		int trackNum = buf[126]; 
		itoa(trackNum, info.Track, 10);

		int genreIdx = buf[127];
		if(genreIdx < g_nMP3GenreCount)
			memcpy(info.Genre, g_arrMP3Genre[genreIdx], sizeof(g_arrMP3Genre[genreIdx]));
		fseek(f, 0, SEEK_END);
		info.Size = ftell(f);
	}
	catch(...)
	{
		error ++;
		goto quit;
	}

quit:
	if(f)
		fclose(f);
	return error;
}

int mp32ogg(char* mp3File, char* oggFile, ConvCallback pConvCallback)
{
	memset(g_ConvInfo.mp3, 0, 255);
	memset(g_ConvInfo.wav, 0, 255);
	memset(g_ConvInfo.ogg, 0, 255);

	int error = 0;
	char wavFile[255] = {0};
	strcpy(wavFile, oggFile);
	strcat(wavFile, ".wav");
	g_ConvCallback = pConvCallback;

	strcpy(g_ConvInfo.wav, wavFile);
	strcpy(g_ConvInfo.mp3, mp3File);
	strcpy(g_ConvInfo.ogg, oggFile);
	if(getmp3info(mp3File, MP3Info) > 0)
	{
		fprintf(stderr, "Failed to get %s info\n", mp3File);			
		error ++;
	}
	g_ConvInfo.stage = 1;	//decode stage
	if(mp3dec(mp3File, wavFile) > 0)
	{
		fprintf(stderr, "Failed to convert %s\n", mp3File);			
		error ++;
		return error;
	}                                     
	g_ConvInfo.stage = 2;	//encode stage
	if(oggenc(wavFile, oggFile, &MP3Info) > 0)
	{
		fprintf(stderr, "Failed to convert %s\n", wavFile);		
		error ++;
		return error;
	}
	return 0;
}

//int _tmain(int argc, _TCHAR* argv[])
//{
//	if(argc < 3)
//		return 0;
//	char* mp3File = argv[1];
//	char* oggFile = argv[2];
//	if(mp32ogg(mp3File, oggFile) > 0)
//	{
//		fprintf(stderr, "Failed to convert %s\n", mp3File);			
//		goto quit;
//	}
//
//quit:
//	getchar();
//	return 0;
//}

